package org.itstack.demo.design.mediator;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: fanYuLe
 * @CreateTime: 2023-12-27  11:28
 * @Description:  SqlSession具体实现类
 * 这⾥包括了接⼝定义的⽅法实现，也就是包装了jdbc层。
 * 通过这样的包装可以让对数据库的jdbc操作隐藏起来，外部调⽤的时候对⼊参、出参都有内部进⾏
 * 处理。
 */
public class DefaultSqlSession implements SqlSession{
    private Connection connection;
    private Map<String,XNode> mapperElement;

    public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
        this.connection = connection;
        this.mapperElement = mapperElement;
    }

    @Override
    public <T> T selectOne(String statement) {
        try {
            XNode xNode = mapperElement.get(statement);
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            ResultSet resultSet = preparedStatement.executeQuery();
            List<T> objects = resultSet20bj(resultSet, Class.forName(xNode.getResultType()));
            return objects.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
       return null;
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        XNode xNode = mapperElement.get(statement);
        Map<Integer, String> paramterMap = xNode.getParameter();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            buildParameter(preparedStatement,parameter,paramterMap);
            ResultSet resultSet = preparedStatement.executeQuery();
            List<T> objects = resultSet20bj(resultSet, Class.forName(xNode.getResultType()));
            return objects.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> List<T> selectList(String statement) {
        XNode xNode = mapperElement.get(statement);
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            ResultSet resultSet = preparedStatement.executeQuery();
            return resultSet20bj(resultSet,Class.forName(xNode.getResultType()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> List<T> selectList(String statement, Object parameter) {
        XNode xNode = mapperElement.get(statement);
        Map<Integer, String> paramterMap = xNode.getParameter();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            buildParameter(preparedStatement,parameter,paramterMap);
            ResultSet resultSet = preparedStatement.executeQuery();
            return resultSet20bj(resultSet,Class.forName(xNode.getResultType()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void close() {
        if(null == connection){
            return;
        }
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private <T> List<T> resultSet20bj(ResultSet resultSet,Class<?> clazz){
        ArrayList<T> list = new ArrayList<>();
        try {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            //每次遍历行值
            while (resultSet.next()) {
                T obj = (T)clazz.newInstance();
                for (int i = 1; i <= columnCount; i++) {
                    Object value = resultSet.getObject(i);
                    String columnName = metaData.getColumnName(i);
                    String setMethod = "set" +columnName.substring(0,1).toUpperCase()+columnName.substring(1);
                    Method method;
                    if(value instanceof Timestamp) {
                        method = clazz.getMethod(setMethod,Date.class);
                    }else {
                        method = clazz.getMethod(setMethod,value.getClass());
                    }
                    method.invoke(obj,value);
                }
                list.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    private void buildParameter(PreparedStatement preparedStatement,Object parameter,Map<Integer,String> parameterMap) throws SQLException, IllegalAccessException {
        int size = parameterMap.size();
        //单个参数
        if(parameter instanceof Long){
            for (int i = 1; i <= size; i++) {
                preparedStatement.setLong(i,Long.parseLong(parameter.toString()));
            }
            return;
        }
        if(parameter instanceof Integer){
            for (int i = 1; i <= size; i++) {
                preparedStatement.setInt(i,Integer.parseInt(parameter.toString()));
            }
            return;
        }
        if(parameter instanceof String){
            for (int i = 1; i <= size; i++) {
                preparedStatement.setString(i,parameter.toString());
            }
            return;
        }
        Map<String,Object> fieldMap = new HashMap<>();
        //对象参数
        Field[] declaredFields = parameter.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            String name = field.getName();
            field.setAccessible(true);
            Object obj = field.get(parameter);
            field.setAccessible(false);
            fieldMap.put(name,obj);
        }

        for (int i = 1; i <= size; i++) {
            String parameterDefine = parameterMap.get(i);
            Object obj = fieldMap.get(parameterDefine);
            if(obj instanceof Short){
                preparedStatement.setShort(i,Short.parseShort(obj.toString()));
                continue;
            }
            if(obj instanceof Integer){
                preparedStatement.setInt(i,Integer.parseInt(obj.toString()));
                continue;
            }
            if(obj instanceof Long){
                preparedStatement.setLong(i,Long.parseLong(obj.toString()));
                continue;
            }
            if(obj instanceof String){
                preparedStatement.setString(i,obj.toString());
                continue;
            }
            if(obj instanceof Date){
                preparedStatement.setDate(i,(Date) obj);
            }
        }
    }
}
